package SemanticAnalyzer; 

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

import Parser.NonTerminal;
import Parser.Parser;
import Parser.Symbol;

/**
 * 
 * @author vcolegro, akanter
 *
 */
public class semanticAnalyzer {
	public ArrayList<HashMap<String, TableEntry>> symT; 
	int depth;
	boolean funLast;
	String currentReturn;  //return type for function which is the root of the current subtree being checked
	static boolean errors = false; //whether there were any errors in the semantic analysis
	boolean expectReturn = false; //indicate if current function must return a value
	long linenumber = 0;
	
	
	/**
	 * constructor
	 */
	public semanticAnalyzer(){
		depth = 0;
		funLast = false; 
		symT = new ArrayList<HashMap<String, TableEntry>>(); //each entry represents a new scope. 
		symT.add(new HashMap<String, TableEntry>() );
	}
	
	
	/**
	 * Constructor with root argument; traverses the tree as well.
	 * @param root root of the parse tree
	 */
	public semanticAnalyzer(Symbol root){
		depth = 0;
		funLast = false; 
		symT = new ArrayList<HashMap<String, TableEntry>>(); //each entry represents a new scope. 
		symT.add(new HashMap<String, TableEntry>() );
		this.traverseTree(root, depth);
	}
	
	public boolean hasErrors(){
		return errors;
	}
	
	/**
     * Main method to create analyzer object.
     * @param args  args[0]: the filename for C source file
     */
    public static void main(String[] args){
    	if (args.length!=1){
    		System.out.println("Invalid input: " + Arrays.deepToString(args) + "\nUsage: SemanticAnalyzer filename\n filename, the full filepath to the C source file to compile");
    		return;
    	}

    	try {
    		Parser parser = new Parser(args[0]); //create parser
    		Symbol root = parser.root;
    		root = parser.parse(new NonTerminal("program"));
    		
    		semanticAnalyzer s = new semanticAnalyzer(); //create analyzer
    		s.traverseTree(root, 0);	
    		if(!errors) System.out.println("No errors!");
    	} catch (IOException e) {
    		System.err.println("File "+args[0]+" Not Found!");
    	}
    }
    
    /**
     * Performs depth-first traversal of tree.
     * @param root
     * @param d
     */
    private void traverseTree(Symbol root, int d){
    	//System.out.println(root.getContent());
    	if (root.isTerminal()){
    		linenumber = root.linenumber;
    		if(root.matches("rbrace")){//remove most recent scope
    			symT.remove(depth);  
    			depth--; 
    		}
    		else if (root.matches("0")){//unknown type (multiple possible)
    			root.semType = "int int* float* void*";
    		}
    		else if(root.getType() == 6){
    			root.semType = "float";
    		}
    		else if(root.getType() > 6){
    			root.semType = "int";
    		}
    		else if(root.getType()==0){ //Check that variable being used is in symbol table.
    			int searchD = depth; 
    			while(searchD>=0){
    				if(symT.get(searchD).containsKey(root.getContent())){ //check if variable is in the table
    					if(!symT.get(searchD).get(root.getContent()).isArray){
        					root.lvalue=true;
    					}
    					else root.lvalue=false;
    					break;
    				}
    				searchD--;
    			}
    			if(searchD < 0){
    				root.semType="error";
    				System.err.println("(" + linenumber + ") " + "Error: variable "+root.getContent()+" not declared.");
    				errors=true;
    			}
    			else {
    				root.semType=symT.get(searchD).get(root.getContent()).type; 
    			}
    		}
    	}

    	else { 
    		if(root.matches("var-declaration")){ //must add variable to symTable
    			int numChil = root.getChildren().size(); 
    			Symbol node = root.getChildren().get(1);
    			if(numChil==3){ //Test if variable is not array/pointer    						
    				symT.get(depth).put(node.getContent(), new TableEntry(node.getContent(), root.getChildren().get(0).getContent()));
    				node.semType=root.getChildren().get(0).toString();
    				node.lvalue=true;
    			}
    			else {
    				int idLoc = 1; //Assumes array is declared as type[num]
    				if (numChil==4) {idLoc = 2;} //assumes declaration is as pointer 
    				if (symT.get(depth).containsKey(root.getChildren().get(idLoc).getContent())){  //if variable already declared in this scope, give error
    					System.err.println("(" + linenumber + ") " + "Error: " + root.getChildren().get(idLoc).getContent() + " already declared in this scope"); 
    					errors=true;
    					root.semType="error";
    				}
    				else{
    					symT.get(depth).put(root.getChildren().get(idLoc).getContent(), new TableEntry(root.getChildren().get(idLoc).getContent(), root.getChildren().get(0).getContent()+"*")); //in the case of a ptr/array
    					if (idLoc==1) symT.get(depth).get(root.getChildren().get(idLoc).getContent()).isArray=true;
    					if (idLoc!=1) root.getChildren().get(idLoc).lvalue=true;
    						
    				}
    			}	   			
    		}
    		else if(root.matches("fun-declaration")){  //Check whether there are params, or child might not exist
    			HashMap<String,TableEntry> hm = symT.get(depth);
    			Symbol node = root.getChildren().get(1);  //node = symbol representing name of function
    			addScope();
    			funLast=true;
    			int paramindex = 3;
    			String returntype = root.getChildren().get(0).getContent();
    			if (root.getChildren().size()==7){
    				paramindex = 4;
    				returntype += "*";
    				node = root.getChildren().get(2);
    			}
    			TableEntry entry = new TableEntry(node.getContent(), returntype, getParams(root.getChildren().get(paramindex)) );
    			
    			//Construct function name with parameter symbols to be put in symbol table
    			StringBuilder sb = new StringBuilder();
    			sb.append(node.getContent()+"@");
    			for (String param : entry.params){  //get specific symbol representation for each parameter (i,f,v,o,g,b for int, float, void, int*, float*, void*)
    				sb.append(getCharRep(param));
    			}
    			
    			hm.put(sb.toString(), entry);
    			this.currentReturn = returntype; //set type for return
    			
    			traverseTree(root.getChildren().get(root.getChildren().size()-1), d+1);
    		}
    		else if(root.matches("compound-stmt")){
    			if(funLast){
    				funLast=false; //new scope has already been added by analysis of function decl
    				//We're expecting a return. 
    				if(!currentReturn.equals("void")){
    					expectReturn=true; 
    				}
    				else {
    					expectReturn=false; 
    				}
    				for(Symbol child : root.getChildren()){
    					traverseTree(child, d+1);
    					if (child.semType!=null && child.semType.equals("validReturn")){
    						expectReturn=false;
    						root.semType="validReturn";
    					}					
    				}
       				if (root.semType==null) root.semType = "noReturn";
    				if(expectReturn){
    					System.err.println("(" + linenumber + ") " + "Error: Missing valid return statement of type " + currentReturn + ".");
    					errors = true;
    				}
    			}
    			else{
    				addScope();
    				for(Symbol child : root.getChildren()){
    					traverseTree(child, d+1);
    					if (child.semType!=null && child.semType.equals("validReturn")) root.semType="validReturn";
    				}
    				if (root.semType==null) root.semType = "noReturn";
    			}
    		}
    		
    		else if (root.matches("statement-list")){
				for(Symbol child : root.getChildren()){
					traverseTree(child, d+1);
					if(child.semType!=null && child.semType.equals("validReturn")){
						root.semType="validReturn";
					}
				}
    		}
    		
    		else if(root.matches("additive-expr")){
    			//children 0 and 2 need to match type rules, check for additional kids
    			for(int i = 0; i < root.getChildren().size(); i++){
    				traverseTree(root.getChildren().get(i), d+1); //initialize type tracker in all reals/constants
    			}
    			int pos = 0; 
    			int opPos = 1; 
    			String resultType = getResultType(root, pos, opPos); //get result type of first operation

    			while(opPos+2 < root.getChildren().size()){ //Check series of operands and operators
    				pos += 2; 
    				opPos += 2; 
    				resultType=getResultType(root, resultType, pos, opPos);
    				if(resultType =="error"){
    					System.err.println("(" + linenumber + ") " + "Error: Operand type mismatch for "+root.getChildren().get(opPos).getContent()+", with types "+root.getChildren().get(pos).semType+", and "+root.getChildren().get(pos+2).semType);
    					errors=true;
    				}
    				break; 
    			}
    			root.semType = resultType;
    		}
    		else if(root.matches("term")){
    			for(int i = 0; i < root.getChildren().size(); i++){
    				traverseTree(root.getChildren().get(i), d+1); //initialize type tracker in all reals/constants
    			}
    			int pos = 0; 
    			int opPos = 1; 
    			String resultType = getMultResult(root, pos, opPos); //get result type of first operation

    			while(opPos+2 < root.getChildren().size()){ //Check series of operands and operators
    				pos += 2; 
    				opPos += 2; 
    				resultType=getMultResult(root, resultType, pos, opPos);
    				if(resultType =="error"){
    					System.err.println("(" + linenumber + ") " + "Error: Operand type mismatch for "+root.getChildren().get(opPos).getContent()+", with types "+root.getChildren().get(pos).semType+", and "+root.getChildren().get(pos+2).semType);
    					errors=true;
    				}
    				break; 
    			}
    			root.semType = resultType;
    		}
    		else if(root.matches("relational-expr")){
    			for(int i = 0; i < root.getChildren().size(); i++){
    				traverseTree(root.getChildren().get(i), d+1); //initialize type tracker in all reals/constants
    			}
    			int pos = 0; 
    			int opPos = 1; 
    			String resultType = getBoolResult(root, pos, opPos); //get result type of first operation

    			while(opPos+2 < root.getChildren().size()){ //Check series of operands and operators
    				if(root.getChildren().get(pos+4).semType.equals(resultType)){
    					resultType = "int";
    					pos+=2;
    					opPos+=2;
    				}
    				else {
    					System.err.println("(" + linenumber + ") " + "Error: Operand type mismatch for "+root.getChildren().get(opPos).getContent()+", with types int, and "+root.getChildren().get(pos+4).semType);
    					errors=true;
    					break;
    				}
    			}
    			root.semType = resultType;

    		}
    		else if(root.matches("unary-expr")){  
    			for(Symbol child : root.getChildren()){
    				traverseTree(child, d+1);
    			}
    			if(root.getChildren().get(0).getContent().equals("*")){
    				root.lvalue =true; 
    				if (root.getChildren().get(1).semType.equals("int*")){
    					root.semType="int";
    				}
    				else if (root.getChildren().get(1).semType.equals("float*")){
    					root.semType="float";
    				}
    				else {
    					root.lvalue=false; 
    					root.semType = "error";
    					System.err.println("(" + linenumber + ") " + "Type mismatch: cannot apply * operator to type " + root.getChildren().get(1).semType);
    					errors=true;
    				}
    			}
    			else if(root.getChildren().get(0).getContent().equals("&")){
    				root.lvalue =true; 

    				if (root.getChildren().get(1).semType.equals("int")){
    					root.semType="int*";
    				}
    				else if (root.getChildren().get(1).semType.equals("float")){
    					root.semType="float*";
    				}
    				else {
        				root.lvalue =false; 
    					root.semType = "error";  
    					System.err.println("(" + linenumber + ") " + "Type mismatch: cannot apply & operator to type " + root.getChildren().get(1).semType);
    					errors=true;
    				}
    			}
    			else if(root.getChildren().get(0).equals("!")){
    				root.semType="int";
    			}
    			else {
    				root.semType = root.getChildren().get(1).semType;  //This is the case where the unary operator is a "+" or "-"
    			}
    		}
    		else if(root.matches("and-expr") || root.matches("or-expr")){
    			for(Symbol child : root.getChildren()){
    				traverseTree(child, d+1);
    			}
    			root.semType="int";
    		}
    		else if(root.matches("if-stmt") || root.matches("while-stmt")){
    			traverseTree(root.getChildren().get(2), d+1);
    			if(root.getChildren().get(2).semType.equals("void")){
    				System.err.println("(" + linenumber + ") " + "Error: Conditional expression has type void."); 
    				root.semType="error";
    				errors=true;
    			}
    			else{
    				for (int i = 3; i<root.getChildren().size(); i++){
    					traverseTree(root.getChildren().get(i), d+1);
    				}
    				if(root.matches("if-stmt") && root.getChildren().size()>6 && root.getChildren().get(4).semType.equals("validReturn") && root.getChildren().get(6).semType.equals("validReturn")){
    					root.semType="validReturn";
    				}
    				else{
    					if(root.matches("while-stmt")){
    						root.semType=(root.getChildren().get(4).semType);
    					}
    				}
    					
    			}
    		}
    		
    		else if(root.matches("for-stmt")){
    			for(Symbol child : root.getChildren()){
    				traverseTree(child, d+1);
    			}
    			root.semType= root.getChildren().get(root.getChildren().size()-1).semType;
    		}
    	
    		else if(root.matches("expression")){
    			int size = root.getChildren().size(); 
    			for(int i = size; i > 0; i--){
    				traverseTree(root.getChildren().get(i-1), d+1);
    			}

    			int pos = size-1;
    			String resultType = "error"; //error until found to be valid
    			
    			//Check if assignment is valid for 2 identifiers/values closest to the right
    			if(root.getChildren().get(pos-2).lvalue){ //if E1 is lvalue
    				if(root.getChildren().get(pos).semType.equals(root.getChildren().get(pos-2).semType) 
    						|| ((root.getChildren().get(pos).semType.contains("int*") ||(root.getChildren().get(pos).semType.contains("float*"))) && root.getChildren().get(pos-2).semType.contains("void*"))
    						|| (root.getChildren().get(pos-2).semType.equals("float*") && root.getChildren().get(pos).semType.contains("float*"))
    						|| (root.getChildren().get(pos-2).semType.contains("int") && root.getChildren().get(pos).semType.contains("int"))
    						){ //if E1 and E2 have compatible types
    					resultType = root.getChildren().get(pos-2).semType; 
    				}
    				else {
    					System.err.println("(" + linenumber + ") " + "Invalid assignment: l-value of type "+root.getChildren().get(pos-2).semType+" is not compatible with r-value of type "+root.getChildren().get(pos).semType);
    					errors=true;
    				}
    			}
    			else{
    				System.err.println("(" + linenumber + ") " + "Invalid assignment: "+root.getChildren().get(pos-2).getContent() +" is not an l-value");
    				errors=true;
    			}

    			pos-=2; 
    			while(pos-1>=0){  //repeat checking against the rest of the variables (moving left)
    				if(!root.getChildren().get(pos).semType.equals(resultType)){
    					root.getChildren().get(pos).semType = "error";
    					System.err.println("(" + linenumber + ") " + "Invalid assignment: l-value of type "+root.getChildren().get(pos).semType+" is not compatible with r-value of type "+resultType);
    					errors=true;
    					break;
    				}
    				else if(!root.getChildren().get(pos).lvalue){
    					root.getChildren().get(pos).semType = "error";
    					System.err.println("(" + linenumber + ") " + "Invalid assignment: "+root.getChildren().get(pos).getContent()+" is not an l-value."); 
    					errors=true;
    					break;
    				}
    				pos-=2;
    			}
    			root.semType=resultType;
    			root.lvalue=false;
    		}

    		else if(root.matches("call")){
    			traverseTree(root.getChildren().get(2), d+1);
    			String lookup; //name to search for in symT

    			String argchars = getArgumentRep(root.getChildren().get(2)); //list of args to mine for types
    			lookup = root.getChildren().get(0).getContent()+"@"+argchars; 

    			ArrayList<String> lookuplist = generateLookup(lookup); //all possible function keys in Symbol Table hashmap, given the arguments

    			HashMap<String, TableEntry> hm = symT.get(0);
    			for(String elt : lookuplist){
    				if(hm.containsKey(elt)){
    					root.semType=hm.get(elt).type;  //sets value of this node to be the value of the function's return
    					break;
    				}	
    			}
    			if(root.semType==null || root.semType.equals("invalid")){
    				System.err.println("(" + linenumber + ") " + "Warning: Function "+root.getChildren().get(0).getContent() + " not declared with given arguments " + parseArgs(lookup));
    				root.semType="int int* float float* void void*";
    			}
    			
    			//Below was for Phase 3; Phase 4 allows undeclared function calls
/*    			if(root.semType==null){ //Exited loop without having found the function in the symbol table.
    				System.out.println(lookup);
    				System.err.println("(" + linenumber + ") " + "Error: Function "+root.getChildren().get(0).getContent() + " not declared with given arguments " + parseArgs(lookup));
    				root.semType="error";
    				errors=true;
    			}*/
    		}
    		else if(root.matches("postfix-expr")){
    			for(Symbol elt: root.getChildren()){
    				traverseTree(elt, d+1);
    			}
    			if(!root.getChildren().get(0).lvalue  && root.getChildren().size()<4){ //check to make sure expression is l-value
					System.err.println("(" + linenumber + ") " + "Error: Expression "+root.getChildren().get(0).getContent()+" must be an l-value.");
					root.semType="error";
					errors=true;
    			}
    			else if(root.getChildren().size() >= 4 ){  //This is an array reference
    				int searchD = depth; 
    				while(searchD>=0){
    					HashMap<String, TableEntry> hm = symT.get(searchD);
    					if(hm.containsKey(root.getChildren().get(0).getContent())){
    						//String type2 = hm.get(root.getChildren().get(0).getContent()).type.substring(0, hm.get(root.getChildren().get(0).getContent()).type.length());
    						String type2 = hm.get(root.getChildren().get(0).getContent()).type;
    						root.semType = type2;
    						break;
    					}
    					searchD--;
    				}	
    				if(searchD<0){ //Exited loop without having found the function in the symbol table.
    					System.err.println("(" + linenumber + ") " + "Error: Array "+root.getChildren().get(0)+" not declared.");
    					root.semType="error";
    					errors=true;
    				}

    				else {//if Array has been declared, we do this
    					traverseTree(root.getChildren().get(2), d+1); 
    					if(!root.getChildren().get(2).semType.contains("int ") && !root.getChildren().get(2).semType.equals("int") ){
    						System.err.println("(" + linenumber + ") " + "Error: Array subscript must be of type int; given type "+root.getChildren().get(2).semType);
    						errors=true;
    						root.semType="error";
    					}
    					else{
    						root.lvalue=true; 
    					}
    				}
    			}
    			else root.semType=root.getChildren().get(0).semType;
    		}
    		else if (root.matches("return-stmt")){
    			if(root.getChildren().size()==2){
    				if(!currentReturn.equals("void")){
        				System.err.println("(" + linenumber + ") " + "Error: Invalid return type <null>, function expects return type " + currentReturn);
        				root.semType = "error";
        				errors=true;
    				}
    				else root.semType = "validReturn";
    			}
    			else{	//3 children; there is a return value
    				traverseTree(root.getChildren().get(1), d+1);
    				if(!root.getChildren().get(1).semType.contains(currentReturn)){
    					System.err.println("(" + linenumber + ") " + "Error: Invalid return type " + root.getChildren().get(1).semType + ". function expects return type " + currentReturn);  //should catch the case where the function is returning a value when it should be returning void
    					root.semType = "error";
    					errors=true;
    				}
    				root.semType = "validReturn";
    			}
    		}
    		else{
    			for(Symbol child : root.getChildren()){
    				traverseTree(child, d+1);
    			}
    		}	
    	}//end case of being nonTerminal
    }   
    
    
    
    /**
     * @param str input (including variable characters "0" and "`") to turn into key for SymbolTable
     * @return an ArrayList of possible lookup strings with the given input
     */
    private ArrayList<String> generateLookup(String str){
    	
    	if(str.indexOf('@')==str.length()-1){
    		ArrayList<String> ret = new ArrayList<String>();
    		ret.add(str+'v');
    		return ret;
    	}
    	
    	ArrayList<ArrayList<String>> pieces = new ArrayList<ArrayList<String>>();
    	int subStringStart = 0;
    	int pieceNumber = 0;
    	
    	for (int i = str.indexOf('@')+1; i<str.length(); i++){
    		if (str.charAt(i)=='0'){ 
    			ArrayList<String> temppieces = new ArrayList<String>();
    			temppieces.add(str.substring(subStringStart, i) + 'i');
    			temppieces.add(str.substring(subStringStart, i) + 'o');
    			temppieces.add(str.substring(subStringStart, i) + 'g');
    			temppieces.add(str.substring(subStringStart, i) + 'b');
    			pieces.add(temppieces);
    		}
    		if (str.charAt(i)=='`'){
    			ArrayList<String> temppieces = new ArrayList<String>();
    			temppieces.add(str.substring(subStringStart, i) + 'i');
    			temppieces.add(str.substring(subStringStart, i) + 'o');
    			temppieces.add(str.substring(subStringStart, i) + 'g');
    			pieces.add(temppieces);
    		}
    		
    		if (str.charAt(i)=='`' || str.charAt(i)=='0'){
    			pieceNumber++;
    			subStringStart=i+1;
    		}
    		else if(i==str.length()-1){
    			ArrayList<String> temppieces = new ArrayList<String>();
    			temppieces.add(str.substring(subStringStart, i+1));
    			pieces.add(temppieces);
    			pieceNumber++;
    		}
    		
    	}
    	
    	ArrayList<String> temp = new ArrayList<String>();
    	ArrayList<String> results = pieces.get(0);
    	for(int i=1;i<pieceNumber;i++){
    		for(String ss1 : results){
        		for(String ss2 : pieces.get(i)){
        			temp.add(ss1+ss2);
        		}
    		}
    		results=temp;
    		temp.clear();
    	}
    	
    	return results;
    }   
    

    /**
     * 
     * @param sym symbol to get arguments from, typically an arg-list
     * @return the character representation used in the function names @see getCharRep
     */
/*    public String getArgumentRep(Symbol sym){
    	if(sym.getChildren()==null){
    		if (sym.semType!=null)
    			return getCharRep(sym.semType);
    		else
    			return "";
    	}
    	else{
    		StringBuffer sb = new StringBuffer();
    		for(Symbol elt : sym.getChildren()){
    			sb.append(getArgumentRep(elt));
    		}
    		return sb.toString();
    	}
    }*/
    
    public String getArgumentRep(Symbol sym){
    	if(sym.getContent().equals("rparen")){
    		return "";
    	}
    	if(sym.getChildren()==null){
    		return getCharRep(sym.semType);
    	}
    	StringBuffer sb = new StringBuffer();
    	for(Symbol elt : sym.getChildren()){
    		sb.append(getCharRep(elt.semType));
    	}
    	return sb.toString();
    }

    
    /**
     * 
     * @param type
     * @return single character used in function names in the symbol table. e.g., foo(int x, float* y) has a key of foo@ig
     */
    public String getCharRep(String type){
    	if(type.equals("int*")) {
			return "o";
		}
		else if(type.equals("int")) {
			return "i";
		}
		else if(type.equals("float*")) {
			return "g";
		}
		else if(type.equals("float")) {
			return "f";
		}
		else if(type.equals("void*")) {
			return "b";
		}
		else if(type.equals("void")) {
			return "v";
		}	
		else if(type.equals("int int* float* void*")){
			return "0";
		}
		else if(type.equals("int int* float*")){
			return "`";
		}
		else return ""; 
    }
    
    /**
     * 
     * @param function the key for a function in the symbol table
     * @return a String with arguments of the given function
     */
    public String parseArgs(String function){
    	int i = function.indexOf('@')+1;
    	StringBuilder sb = new StringBuilder();
    	
    	for (;i<function.length();i++){
    		char current = function.charAt(i);
    		switch(current){
    		case 'o':
    			sb.append("int* ");
    			break;
    		case 'i':
    			sb.append("int ");
    			break;
    		case 'g':
    			sb.append("float* ");
    			break;
    		case 'f':
    			sb.append("float ");
    			break;
    		case 'b':
    			sb.append("void* ");
    			break;
    		case 'v':
    			sb.append("void ");
    			break;
    		case '0':
    			sb.append("0");
    			break;
    		case '`':
    			sb.append("int/int*/float*");
    			break;
    		default:
    			break;
    		}
    	}
    	if (sb.length()>=1 && sb.charAt(sb.length()-1)==' ')
    		sb.deleteCharAt(sb.length()-1);
    	
    	return sb.toString();
    }
        
    /**
     * Returns "int" if operands are of same type; else returns "error"
     * @param root
     * @param pos
     * @param opPos
     * @return
     */
    private String getBoolResult(Symbol root, int pos, int opPos){
    	String left = root.getChildren().get(0).semType; 
    	String right = root.getChildren().get(2).semType;
    	
    	if(left.equals(right) || (left.length() > 6 && left.contains(right) && (!right.equals("float"))) ||  (right.length() > 6 && right.contains(left) && (!left.equals("float")) ) ) {
    		return "int";
    	}
    	errors=true;
    	System.err.println("(" + linenumber + ") " + "Error: types "+left+" and "+right+" are not compatible for relational operator "+root.getChildren().get(opPos) +".");
    	return "error";
    }    

    /**
     * Method to get result type of a mult operation (*,/,%).
     * @param root
     * @param pos
     * @param opPos
     * @return
     */
    private String getMultResult(Symbol root, int pos, int opPos){
    	String initPos = root.getChildren().get(pos).semType; 
		return getMultResult(root, initPos, pos, opPos);
    }
    
    /**
     * Helper
     * @param root
     * @param type
     * @param pos
     * @param opPos
     * @return
     */
    private String getMultResult(Symbol root, String type, int pos, int opPos){
    	if(type.contains("int")){
			if(root.getChildren().get(pos+2).semType.contains("int")){
				return "int";
			}
    	}
    	else if(type.equals("float")){
    		if(root.getChildren().get(pos+2).semType.equals("float") && !root.getChildren().get(opPos).getContent().equals("%")){
				return "float";
			}
    	}
    	errors=true;
    	System.err.println("(" + linenumber + ") " + "Error: type "+root.getChildren().get(pos+2).semType+" and "+type+" are not compatible for operator " + root.getChildren().get(opPos).getContent());
    	return "error";  
    }
    
    /**
     * Check sequence of operand types
     * @param root
     * @param pos
     * @param opPos
     * @return
     */
	private String getResultType(Symbol root, int pos, int opPos){
		String initPos = root.getChildren().get(pos).semType; 
		
		return getResultType(root, initPos, pos, opPos);
	}
				
	/**
	 * Helper method to check those operand beyond the first two.
	 * @param root
	 * @param type
	 * @param pos
	 * @param opPos
	 * @return
	 */
	private String getResultType(Symbol root, String type, int pos, int opPos){	
		if(type.equals("int")){
			if(root.getChildren().get(pos+2).semType.equals("int")){
				return "int";
			}
			else if (root.getChildren().get(pos+2).semType.equals("int int* float* void*")){
				return "int int* float*";
			}
			else if(root.getChildren().get(pos+2).semType.equals("int*") &&  (root.getChildren().get(opPos).matches("+"))){ 
				return "int*";
			}
			else if(root.getChildren().get(pos+2).semType.equals("float*") &&  (root.getChildren().get(opPos).matches("+"))){
				return "float*";
			}
		}
		else if(type.equals("float")){
			if(root.getChildren().get(pos+2).semType.equals("float")){
				return "float";
			}
			else if (root.getChildren().get(pos+2).semType.equals("int int* float* void*")){
				 return "float";
			}
		}
		else if(type.equals("float*")){
			if(root.getChildren().get(pos+2).semType.equals("int")){
				return "float*";
			}
			else if (root.getChildren().get(pos+2).semType.equals("int int* float* void*")){
				return "float*";
			}
			else if  (root.getChildren().get(pos+2).semType.equals("float*") &&  (root.getChildren().get(opPos).matches("-"))){ 
				return "int";
			}
		}
		else if(type.equals("int*")){
			if(root.getChildren().get(pos+2).semType.equals("int")){
				return "int*";
			}
			else if (root.getChildren().get(pos+2).semType.equals("int int* float* void*")){
				return "int*";
			}
			else if  (root.getChildren().get(pos+2).semType.equals("int*") &&  (root.getChildren().get(opPos).matches("-"))){ 
				return "int";
			}
		}
		else if(type.equals("int int* float* void*")){
			if(root.getChildren().get(pos+2).semType.equals("int")){
				return "int int* float*";
			}
			else if(root.getChildren().get(pos+2).semType.equals("int*")){
				return "int*";
			}
			else if(root.getChildren().get(pos+2).semType.equals("float*")){
				return "float*";
			}
			else if(root.getChildren().get(pos+2).semType.equals("int int* float* void*")){
				return "int int* float* void*";
			}
		}
		errors=true;
    	System.err.println("(" + linenumber + ") " + "Error: type "+root.getChildren().get(pos+2).semType+" and "+ type +" are not compatible for operator " + root.getChildren().get(opPos).getContent());

		return "error"; 
	}
    
    /**
     * Method to add additional scope.
     */
    private void addScope(){
    	depth++;
		symT.add(new HashMap<String, TableEntry>()); //adds new scope
    }   
    
    /**
     * Method to lift out function param types.
     * @param root
     * @return ArrayList<String> of parameter types
     */
    private ArrayList<String> getParams(Symbol root){
    	funLast = true; 
    	if(root.matches("void")){
    		root.semType="void";
    		ArrayList<String> ret = new ArrayList<String>();
    		ret.add("void");
    		return ret;
    	}
    	if(root.matches("param")){
    		//add type of argument to the param list
    		ArrayList<String> params = new ArrayList<String>();
    		if(root.getChildren().size()>2)
    			params.add(root.getChildren().get(0).getContent()+"*");
    		else
    			params.add(root.getChildren().get(0).getContent());

    		//add variable to the function's scope
    		int numChil = root.getChildren().size(); 
    		Symbol node = root.getChildren().get(1);
    		int idLoc = 1;
    		if(numChil==2){ //Test if variable is array/pointer	
    			symT.get(depth).put(node.getContent(), new TableEntry(node.getContent(), root.getChildren().get(0).getContent())); //add to most recent scope the param, as an entry
    		}
    		else {
    			if (numChil==3) idLoc = 2;
    			symT.get(depth).put(root.getChildren().get(idLoc).getContent(), new TableEntry(root.getChildren().get(idLoc).getContent(), root.getChildren().get(0).getContent()+"*")); //in the case of a ptrarray
    		}
    		return params;
    	}
    	else {
    		//Call getParams on rest of children; merge their results into one arraylist. return that.
    		ArrayList<String> returnparams = getParams(root.getChildren().get(2));
    		returnparams.addAll(0, getParams(root.getChildren().get(0)));
    		return returnparams;
    	}
    }
}  
 